define(async () => {
    const util = await require("../util");

    //运动算法
    const TimingFunction = {
        linear: (t,b,c,d) => c * t / d + b,
        quad: {
            easeIn: (t,b,c,d) => c * (t /= d) * t + b,
            easeOut: (t,b,c,d) => -c * (t /= d) * (t - 2) + b,
            easeInOut: (t,b,c,d) => (t /= d / 2) < 1 ? (c / 2 * t * t + b) : (-c / 2 * ((--t) * (t - 2) - 1) + b)
        },
        cubic: {
            easeIn: (t,b,c,d) => c * (t /= d) * t * t + b,
            easeOut: (t,b,c,d) => c * ((t = t / d - 1) * t * t + 1) + b,
            easeInOut: (t,b,c,d) => (t /= d / 2) < 1 ? (c / 2 * t * t * t + b) : (c / 2 * ((t -= 2) * t * t + 2) + b)
        },
        quart: {
            easeIn: (t,b,c,d) => c * (t /= d) * t * t * t + b,
            easeOut: (t,b,c,d) => -c * ((t = t/d - 1) * t * t * t - 1) + b,
            easeInOut: (t,b,c,d) => (t /= d / 2) < 1 ? (c / 2 * t * t * t * t + b) : (-c / 2 * ((t -= 2) * t * t * t - 2) + b)
        },
        quint: {
            easeIn: (t,b,c,d) => c * (t /= d) * t * t * t * t + b,
            easeOut: (t,b,c,d) => c * ((t = t / d - 1) * t * t * t * t + 1) + b,
            easeInOut: (t,b,c,d) => (t /= d / 2) < 1 ? (c / 2 * t * t * t * t * t + b) : (c / 2 * ((t -= 2) * t * t * t * t + 2) + b)
        },
        sine: {
            easeIn: (t,b,c,d) =>  -c * Math.cos(t / d * (Math.PI / 2)) + c + b,
            easeOut: (t,b,c,d) => c * Math.sin(t / d * (Math.PI / 2)) + b,
            easeInOut: (t,b,c,d) => -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b
        },
        expo: {
            easeIn: (t,b,c,d) => t === 0 ? b : (c * Math.pow(2, 10 * (t / d - 1)) + b),
            easeOut: (t,b,c,d) => t === d ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b,
            easeInOut: (t,b,c,d) => {
                if(t === 0) return b;
                if(t === d) return b + c;
                if((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
                return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
            }
        },
        circ: {
            easeIn: (t,b,c,d) => -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b,
            easeOut: (t,b,c,d) => c * Math.sqrt(1 - (t = t / d - 1) * t) + b,
            easeInOut: (t,b,c,d) => (t /= d / 2) < 1 ? (-c / 2 * (Math.sqrt(1 - t * t) - 1) + b) : (c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b)
        },
        elastic: {
            easeIn: (t,b,c,d,a,p) => {
                if(t === 0) return b;
                if((t /= d) === 1) return b + c;
                if(!p) p = d * 0.3;
                let s;
                if (!a || a < Math.abs(c)) {
                    a = c;
                    s = p / 4;
                }
                else
                    s = p / (2 * Math.PI) * Math.asin(c / a);
                return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p )) + b;
            },
            easeOut: (t,b,c,d,a,p) => {
                if(t === 0) return b;
                if((t /= d) === 1) return b + c;
                if(!p) p = d * 0.3;
                let s;
                if (!a || a < Math.abs(c)) {
                    a = c;
                    s = p / 4;
                }
                else
                    s = p / (2 * Math.PI) * Math.asin(c / a);
                return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
            },
            easeInOut: (t,b,c,d,a,p) => {
                if(t === 0) return b;
                if((t /= d / 2) === 2) return b + c;
                if(!p) p = d * (0.3 * 1.5);
                if (!a || a < Math.abs(c)) {
                    a = c;
                    s = p / 4;
                }
                else
                    s = p / (2 * Math.PI) * Math.asin(c / a);
                if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
                return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
            }
        },
        back: {
            easeIn: (t,b,c,d,s) => {
                if(util.isUndefined(s)) s = 1.70158;
                return c * (t /= d) * t * ((s + 1) * t - s) + b;
            },
            easeOut: (t,b,c,d,s) => {
                if(util.isUndefined(s)) s = 1.70158;
                return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
            },
            easeInOut: (t,b,c,d,s) => {
                if(util.isUndefined(s)) s = 1.70158;
                return (t /= d / 2) < 1 ? (c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b) : (c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b)
            }
        },
        bounce: {
            easeIn: (t,b,c,d) => c - TimingFunction.bounce.easeOut(d - t, 0, c, d) + b,
            easeOut: (t,b,c,d) => {
                if((t /= d) < (1 / 2.75)) return c * (7.5625 * t * t) + b;
                else if(t < (2 / 2.75)) return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b;
                else if(t < (2.5 / 2.75)) return c * (7.5625 * (t -= (2.25/2.75)) * t + 0.9375) + b;
                else return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b;
            },
            easeInOut: (t,b,c,d) => t < d / 2 ? (TimingFunction.bounce.easeIn(t * 2, 0, c, d) * 0.5 + b) : (TimingFunction.bounce.easeOut(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b)
        }
    }

    class Animation {

        targetX;
        targetY;
        targetAngle;
        targetScale;
        targetScaleX;
        targetScaleY;
        time = 0;
        duration;
        completed = false;
        _timingFunction;

        constructor(options = {}) {
            util.assert(util.isObject(options), new TypeError("options must be an Object"));
            const { targetX, targetY, targetAngle, targetScale, targetScaleX, targetScaleY, duration, timingFunction } = options;
            util.assert(util.isUndefined(targetX) || util.isNumber(targetX), new TypeError("targetX must be an Number"));
            util.assert(util.isUndefined(targetY) || util.isNumber(targetY), new TypeError("targetY must be an Number"));
            util.assert(util.isUndefined(targetAngle) || util.isNumber(targetAngle), new TypeError("targetAngle must be an Number"));
            util.assert(util.isUndefined(targetScaleX) || util.isNumber(targetScaleX), new TypeError("targetScaleX must be an Number"));
            util.assert(util.isUndefined(targetScaleY) || util.isNumber(targetScaleY), new TypeError("targetScaleY must be an Number"));
            util.assert(util.isUndefined(duration) || util.isNumber(duration), new TypeError("duration must be an Number"));
            util.assert(util.isUndefined(timingFunction) || util.isString(timingFunction), new TypeError("timingFunction must be an String"));
            this.targetX = targetX;
            this.targetY = targetY;
            this.targetAngle = targetAngle;
            this.targetScaleX = util.defaultTo(targetScaleX, targetScale);
            this.targetScaleY = util.defaultTo(targetScaleY, targetScale);
            this.duration = util.defaultTo(duration, 1000);
        }

        update(node) {
            if(!this.timingFunction)
                this.timingFunction = "linear";
            // if(!util.isNumber(this.targetX))
            //     this.targetX = node.x;
            // if(!util.isNumber(this.targetY))
            //     this.targetY = node.y;
            // if(!util.isNumber(this.targetAngle))
            //     this.targetAngle = node.rotate;
            // if(!util.isNumber(this.targetScaleX))
            //     this.targetScaleX = node.scaleX;
            // if(!util.isNumber(this.targetScaleY))
            //     this.targetScaleY = node.scaleY;
            if(util.isNumber(this.targetX))
                node.x = this.timingFunction(this.time, node.x, this.targetX - node.x, this.duration);
            if(util.isNumber(this.targetY))
                node.y = this.timingFunction(this.time, node.y, this.targetY - node.y, this.duration);
            if(util.isNumber(this.targetAngle))
                node.rotate = this.timingFunction(this.time, node.rotate, this.targetAngle - node.rotate, this.duration);
            if(util.isNumber(this.targetScaleX))
                node.scaleX = this.timingFunction(this.time, node.scaleX, this.targetScaleX - node.scaleX, this.duration);
            if(util.isNumber(this.targetScaleY))
                node.scaleY = this.timingFunction(this.time, node.scaleY, this.targetScaleY - node.scaleY, this.duration);
            this.time++;
            const errorValue = Math.abs(node.x - (util.isNumber(this.targetX) ? this.targetX : node.x)) + Math.abs(node.y - (util.isNumber(this.targetY) ? this.targetY : node.y)) + Math.abs(node.rotate - (util.isNumber(this.targetAngle) ? this.targetAngle : node.rotate)) + Math.abs(node.scaleX - (util.isNumber(this.targetScaleX) ? this.targetScaleX : node.scaleX)) + Math.abs(node.scaleY - (util.isNumber(this.targetScaleY) ? this.targetScaleY : node.scaleY));
            if(errorValue <= 0.1)
                this.completed = true;
        }

        reset() {
            this.completed = false;
            this.time = 0;
        }

        set timingFunction(value) {
            util.assert(util.isString(value), new TypeError("timingFunction must be an String"));
            if(!this._timingFunction)
                this._timingFunction = TimingFunction;
            const chunks = value.split(".");
            chunks.forEach(chunk => {
                util.assert(this._timingFunction[chunk], `timingFunction ${value} is not supported`);
                this._timingFunction = this._timingFunction[chunk];
            });
        }

        get timingFunction() {
            return this._timingFunction;
        }

        static isInstance(value) {
            return value instanceof Animation;
        }

    }

    return Animation;
});